package com.code.origin.utils.encryption;

import com.sankuai.meituan.waimai.opensdk.exception.ApiSysException;
import com.sankuai.meituan.waimai.opensdk.util.SignGenerator;
import com.sankuai.meituan.waimai.opensdk.vo.SystemParam;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

//import cn.jiuxiaoer.third.meituan.MeituanApplication;

/**
<p>	 计算签名
<p>在调用接口进行编码前，开发者需先计算签名，计算方式如下：

<p>将所有参数（sig除外）按照参数名的字母顺序排序，并用&连接： app_id=1235123121&app_poi_code=31&timestamp=1389751221
<p>按照请求url + ? + 排序后的参数 + consumer_secret的顺序进行连接，得到加密前的字符串: 
<li>http://waimaiopen.meituan.com/api/v1/poi/mget?app_id=1235123121&app_poi_codes=31&timestamp=1389751221d31ba58fd73c71db697ab5e4946d52d</li>

<p>注：参数中包含中文时，处理方法：中文保持原文即可，无需对其单独转码，示例(参数未列全部)如下：
<li>http://waimaiopen.meituan.com/api/v1/poi/save?app_id=1235123121&app_poi_code=31&name=丽华快餐&address=北苑路北站K酷时代广场4层&timestamp=1389751221d31ba58fd73c71db697ab5e4946d52d</li>
<p>对加密前的字符串进行MD5加密，得到签名：00934d00d0aea6f12161edfb6456143d
<p>将得到的签名赋给sig作为请求参数：
<li>http://waimaiopen.meituan.com/api/v1/poi/mget?app_poi_codes=31&app_id=1235123121& timestamp=1389751221&sig=5c29938735e259f287480c2bbaaf2c18</li>
<p>POST、GET及其他请求方式都需使用上述方式计算签名。
<p>注意事项：

<p>美团在优化接口的过程中可能调整或新增字段，请您不要将签名的传输字段写死，以免造成验签失败
<p>美团外卖目前提供了Java SDK，您可直接将文件中的jar包引用到项目中使用
<p>SDK中的index.html是开发的帮助文档，您也可以通过SDK中的源码包帮助开发
<p>计算签名时，中文不需要编码，签名需区分大小写
<p>另外，当APP方接收美团的推送信息时，签名需要解码两次，步骤如下：

<p>utf8解码，此时参数里的中文仍为编码
<p>对中文进行解码
<p>计算签名，请注意没有值的参数也需拼接
 */
@Slf4j
public class SignUtils_01 {
	
    /**
     * 拼接所有业务参数
     * concatParams </br>
     *
     * @param params2
     * @return </br>
     * String
     */
	private static String concatParams(Map<String, String> params2) {
        Object[] key_arr = params2.keySet().toArray();
        Arrays.sort(key_arr);
        String str = "";

        for (Object key : key_arr) {
            if(key.equals("appSecret")){
                continue;
            }
            String val = params2.get(key);
            str += "&" + key + "=" + val;
        }
        return str.replaceFirst("&", "");
    }

    /**
     * 拼接对象属性
     * getProperty </br>
     *
     * @param entityName
     * @return
     * @throws Exception </br>
     *                   Map<String,String>
     */
    public static Map<String, String> getProperty(Object entityName) throws Exception {
        Map<String, String> mapResult = new HashMap<String, String>();
        Class<? extends Object> c = entityName.getClass();
        Field field[] = c.getDeclaredFields();
        for (Field f : field) {
            Object v = invokeMethod(entityName, f.getName(), null);
            
            if ("serialVersionUID".equals(f.getName()) || "sign".equals(f.getName())) {
                continue;
            }
            //空值也要加入
            if(v==null) {
            	mapResult.put(f.getName(), "");
            }else {
            	mapResult.put(f.getName(), v.toString());
            }
            
        }
        return mapResult;
    }

    /**
     * 获取属性值
     * invokeMethod </br>
     *
     * @param owner
     * @param methodName
     * @param args
     * @return
     * @throws Exception </br>
     *                   Object
     */
    private static Object invokeMethod(Object owner, String methodName, Object[] args) throws Exception {
        Class<? extends Object> ownerClass = owner.getClass();
        methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
        Method method = null;
        try {
            method = ownerClass.getMethod("get" + methodName);
        } catch (Exception e) {
            return " can't find 'get" + methodName + "' method";
        }
        return method.invoke(owner);
    }

    /**
     * getSign 获取MD5签名
     *
     * @param param     参数对象
     * @param appSecret 应用secret
     * @return </br>
     * String
     */
    public static String getSign(Object param, String url,String timestamp,SystemParam systemParam) throws Exception {
    	String sig = null;
    	Map<String, String> applicationParamsMap =  new HashMap<>();
    	applicationParamsMap = getProperty(param);
    	
    	applicationParamsMap.put("timestamp",timestamp);
    	
        //生成签名
  		sig = genSig(url,applicationParamsMap,systemParam);
        return sig;
    }
    
	public static void main(String[] args) {
	}
    
	private static String genSig(String url, Map<String, String> applicationParamsMap,SystemParam systemParam) {
		String basedUrl = concatAll(url, applicationParamsMap,systemParam);
		log.info("basedUrl:"+basedUrl);
		String sig = null;
		// 生成签名
		try {
			sig = SignGenerator.genSig(basedUrl);
		} catch (ApiSysException e) {
			log.error("ApiSysException:",e);
		}
		return sig;
	}
    
	private static String concatAll(String url, Map<String, String> applicationParamsMap,SystemParam systemParam) {
		// 组织系统级参数
		Map<String, String> systemParamsMap = new HashMap<>();
		systemParamsMap.put("app_id", systemParam.getAppId());
		systemParamsMap.put("appSecret", systemParam.getAppSecret());

		Map<String, String> paramMap = new HashMap<>();
		paramMap.putAll(systemParamsMap);
		// 如果应用级参数不为空，则组合应用级参数
		if (applicationParamsMap != null) {
			paramMap.putAll(applicationParamsMap);
		}
		String str = concatParams(paramMap);
		String basedUrl = url + str + systemParamsMap.get("appSecret");
		return basedUrl;
	}
	
	/**
	 * MD5加密签名
	 * @param input 
	 * @return
	 */
	public static String getMD5(String input) {
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			byte[] messageDigest = md.digest(input.getBytes());
			BigInteger number = new BigInteger(1, messageDigest);
			String hashtext = number.toString(16);
			while (hashtext.length() < 32) {
				hashtext = "0" + hashtext;
			}
			return hashtext.toUpperCase();
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 校验签名
	 * @param sig
	 * @param timestamp
	 * @param url
	 * @param object
	 * @return
	 */
	public static boolean checkSign(String sig,String timestamp,String url,Object object,SystemParam systemParam) {
		
    	//测试地址签名验证,先不强制验证，美团有新参数加入时，api文档不一定更新，导致签名无法验证通过
    	try {
			String sign = SignUtils.getSign(object, url+"?", timestamp,systemParam);
			log.info(sign);
			if(sig!=null && !sig.equals(sign)) {
				log.info("sig is no match!");
//				return false;
			}
		} catch (Exception e) {
			log.error("getSign",e);
		}
    	return true;
	}
}